﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Security.AccessControl;
using System.Security.Principal;

namespace RouterControl.Whs
{
	public sealed class GlobalMutex : IDisposable
	{
		private static readonly MutexSecurity _security;
		private Mutex _mutex;

		public interface ILockUsing : IDisposable
		{
			bool IsLocked
			{
				get;
			}
		}

		public class ForceLockUsing : ILockUsing
		{
			private GlobalMutex _gm;
			private bool _isLocked = false;

			public bool IsLocked
			{
				get { return _isLocked; }
			}

			internal ForceLockUsing(GlobalMutex gm)
			{
				if (gm == null)
					throw new ArgumentNullException("gm");

				_gm = gm;

				_gm._mutex.WaitOne();

				_isLocked = true;
			}

			public void Dispose()
			{
				if (_gm != null)
					_gm._mutex.ReleaseMutex();

				_isLocked = false;
			}

		}

		public class TryLockUsing : ILockUsing
		{
			private GlobalMutex _gm;
			private bool _isLocked;

			public bool IsLocked
			{
				get { return _isLocked; }
			}

			internal TryLockUsing(GlobalMutex gm, TimeSpan time)
			{
				if (gm == null)
					throw new ArgumentNullException("gm");

				_gm = gm;

				_isLocked = _gm._mutex.WaitOne(time);
			}

			public void Dispose()
			{
				if ((_gm != null) && _isLocked)
					_gm._mutex.ReleaseMutex();

				_isLocked = false;
			}

		}

		/// <summary>
		/// used to force locking over a specific part
		/// 
		/// to be used in conjunction with using() {}
		/// </summary>
		public ForceLockUsing ForceLock
		{
			get { return new ForceLockUsing(this); }
		}

		/// <summary>
		/// tries to lock for a specific amount of time.
		/// if not locking was not successful during that time
		/// period the locking failed (return.IsLocked=false).
		/// 
		/// to be used in conjunction with using() {}
		/// </summary>
		public TryLockUsing TryLock(TimeSpan tryFor)
		{
			return new TryLockUsing(this, tryFor);
		}

		/// <summary>
		/// tries to lock for a specific amount of time.
		/// if not locking was not successful during that time
		/// period the locking failed (return.IsLocked=false).
		/// 
		/// to be used in conjunction with using() {}
		/// </summary>
		public TryLockUsing TryLock(int tryForMilliSeconds)
		{
			return new TryLockUsing(this, new TimeSpan(TimeSpan.TicksPerMillisecond * (long)tryForMilliSeconds));
		}

		public void Dispose()
		{
			if (_mutex != null)
			{
				try
				{
					_mutex.ReleaseMutex();
				}
				catch (ApplicationException)
				{
					// ignore if not owned
				}
			}
		}

		static GlobalMutex()
		{
			var user = new SecurityIdentifier(WellKnownSidType.WorldSid, null);
			var security = new MutexSecurity();

			// allow sync
			{
				var rule = new MutexAccessRule(
					user,
					MutexRights.Synchronize | MutexRights.TakeOwnership,
					AccessControlType.Allow
					);

				security.AddAccessRule(rule);
			}
			
			// disallow permission change
			{
				var rule = new MutexAccessRule(
					user,
					MutexRights.ChangePermissions,
					AccessControlType.Deny
					);

				security.AddAccessRule(rule);
			}

			// done
			_security = security;
		}

		public GlobalMutex(Guid id)
		{
			var mutexName = ("Global\\" + id.ToString("N"));

			// try to open existing
			try
			{
				_mutex = Mutex.OpenExisting(mutexName, MutexRights.Synchronize | MutexRights.TakeOwnership);
			}
			catch (WaitHandleCannotBeOpenedException)
			{
				bool isNew;

				_mutex = new Mutex(
					true,
					mutexName,
					out isNew,
					_security
					);
			}

		}

	}
}
